package net.omnivention.wulumuqi;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;

import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.omnivention.wulumuqi.html.HtmlElementFactory;

import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;

public class HtmlDocument extends Document {
	private final static Logger logger = Logger.getLogger(HtmlDocument.class);

	private static XMLOutputter xmlPrettyPrinter;
	static {
		Format format = Format.getPrettyFormat(); // TODO: create own
		// formatter
		format.setOmitDeclaration(true);
		format.setExpandEmptyElements(true);
		xmlPrettyPrinter = new XMLOutputter(format);
	}

	private BSFManager manager;

	private WuluClassLoader urlClassLoader;

	private String contextURL;

	private ServletContext servletContext;

	private HttpServletRequest request;

	private HttpServletResponse response;

	public HtmlDocument(Element rootElement) {
		super(rootElement);
	}

	public static Element createElement(String elementName) {
		return HtmlElementFactory.createElement(elementName);
	}

	void init(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
		URL[] urls = new URL[1];
		try {
			String baseURL = getBaseURI();
			contextURL = WuluUtil.getContextURL(baseURL);
			urls[0] = new URL(contextURL);
			urlClassLoader = new WuluClassLoader(urls, HtmlDocument.class.getClassLoader());
		} catch (MalformedURLException e) {
			logger.error(e);
		}

		manager = new BSFManager();
		try {
			manager.loadScriptingEngine("jython");
			// manager.loadScriptingEngine("ruby");
			manager.loadScriptingEngine("groovy");
			manager.declareBean("document", this, HtmlDocument.class);
			manager.setClassLoader(urlClassLoader);
			setRequest(request);
			setResponse(response);
			setServletContext(servletContext);
		} catch (BSFException e) {
			logger.error(e);
		}
		mergeFragments();
		applyManipulatorsInMetaTag();
		applyManipulatorTag();
	}

	public Element getElementById(String id) throws JDOMException {
		String xpathQuery = "//*[@id='" + id + "']";
		return selectNode(xpathQuery);
	}

	public List selectNodes(String xpathExpression) {
		try {
			XPath xpath = WuluUtil.createXPath(xpathExpression);
			return xpath.selectNodes(this);
		} catch (JDOMException e) {
			logger.warn(e, e);
		}
		return null;
	}

	public Element selectNode(String xpathExpression) {
		List nodes = selectNodes(xpathExpression);
		if (nodes.size() > 0) {
			return (Element) nodes.get(0);
		}
		return null;
	}

	void applyManipulatorsInMetaTag() {
		Element meta = selectNode("//html:meta[@docManipulators]");
		if (meta != null) {
			String docManipulators = meta.getAttributeValue("docManipulators");
			applyManipulatorsInQueryString(docManipulators);
			meta.detach();
		}
	}

	void applyManipulatorTag() {
		List manipulators = selectNodes("//html:manipulator");
		try {
			for (int i = 0; i < manipulators.size(); i++) {
				Element manipulator = (Element) manipulators.get(i);
				manipulator.detach();

				String script = manipulator.getText();
				String lang = manipulator.getAttributeValue("language");
				if (lang == null || lang.indexOf("python") > -1 || lang.indexOf("jython") > -1) {
					manager.exec("jython", script, 0, 0, script);
				} else if (lang.indexOf("ruby") > -1) {
					manager.exec("ruby", script, 0, 0, script);
				} else if (lang.indexOf("groovy") > -1) {
					manager.exec("groovy", script, 0, 0, script);
				}

			}
		} catch (BSFException e) {
			logger.error(e, e);
		}
	}

	public void applyManipulator(String manipulatorURL) {
		try {
			logger.info("manipulatorURL=" + manipulatorURL);
			String script = WuluUtil.getResource(manipulatorURL);
			if (manipulatorURL.indexOf(".py") > -1) {
				manager.exec("jython", manipulatorURL, 0, 0, script);
			} else if (manipulatorURL.indexOf(".rb") > -1) {
				manager.exec("ruby", manipulatorURL, 0, 0, script);
			} else if (manipulatorURL.indexOf(".groovy") > -1) {
				manager.exec("groovy", manipulatorURL, 0, 0, script);
			}
		} catch (BSFException e) {
			logger.error(e, e);
		}
	}

	/**
	 * comma delimited string of manipulators
	 * 
	 * @param docManipulatorURLs
	 * @throws IOException
	 */
	public void applyManipulatorsInQueryString(String docManipulatorURLs) {
		String[] manipulatorURLs = parseManipulatorURLs(docManipulatorURLs);
		for (int i = 0; i < manipulatorURLs.length; i++) {
			String manURL = manipulatorURLs[i];
			applyManipulator(manURL);
		}
	}

	private String[] parseManipulatorURLs(String docManipulatorsParam) {

		String[] urls = docManipulatorsParam.split(",");
		for (int i = 0; i < urls.length; i++) {
			String url = urls[i];
			if (url.indexOf("http://") == -1) {
				urls[i] = "file:///" + servletContext.getRealPath(url);
			}
		}

		return urls;
	}

	public HttpServletRequest getRequest() {
		return this.request;
	}

	private void setRequest(HttpServletRequest request) {
		try {
			this.request = request;
			manager.declareBean("request", request, HttpServletRequest.class);
			manager.declareBean("input", request.getInputStream(), ServletInputStream.class);
			manager.declareBean("session", request.getSession(), HttpSession.class);
		} catch (BSFException e) {
			logger.error(e);
		} catch (IOException e) {
			logger.error(e);
		}
	}

	private void setResponse(HttpServletResponse response) {
		try {
			this.response = response;
			manager.declareBean("response", response, HttpServletResponse.class);
			manager.declareBean("out", response.getOutputStream(), ServletOutputStream.class);
		} catch (BSFException e) {
			logger.error(e);
		} catch (IOException e) {
			logger.error(e);
		}
	}

	private void setServletContext(ServletContext servletContext) {
		try {
			this.servletContext = servletContext;
			manager.declareBean("servletContext", servletContext, ServletContext.class);
		} catch (BSFException e) {
			logger.error(e);
		}
	}

	@SuppressWarnings("unchecked")
	public boolean validateForm() {
		boolean formValid = true;

		Enumeration names = request.getParameterNames();

		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			if (name.equals("scriptURL") || name.equals("htmlFile")) {
				continue;
			}

			String xpath = "//html:form/descendant::html:input[@name='" + name + "' and @regex]";
			Element input = selectNode(xpath);
			if (input != null) {
				String value = request.getParameter(name);
				input.setAttribute("value", value);
				String regex = input.getAttributeValue("regex");
				if (regex != null) {
					boolean inputValid = Pattern.matches(regex, value);
					logger.debug(inputValid + " " + regex + " " + value);
					if (!inputValid) {
						String onInvalid = input.getAttributeValue("onInvalid");
						if (onInvalid != null) {
							try {
								manager.declareBean("input", input, Element.class);
								manager.eval("jython", "foo.html", 0, 0, onInvalid);
							} catch (BSFException e) {
								logger.error(e, e);
							}
						}
						formValid = false;
					}
				}
			}
		}

		if (formValid == false) {
			Element input = createElement("input");
			input.setAttribute("type", "hidden");
			input.setAttribute("name", "htmlFile");
			String htmlFile = (String) request.getAttribute("htmlFile");
			input.setAttribute("value", htmlFile);
			Element form = selectNode("//html:form");
			form.addContent(input);
			logger.debug(htmlFile);
		}

		return formValid;
	}

	void mergeFragments() {
		Element metas = selectNode("//html:meta[@fragments]");

		if (metas != null) {
			List<HtmlDocument> fragmentList = new ArrayList<HtmlDocument>();
			String[] frags = metas.getAttributeValue("fragments").split(",");
			for (int i = 0; i < frags.length; i++) {
				String frag = frags[i];
				if (frag.indexOf("http://") == -1) {
					frag = "file:///" + servletContext.getRealPath(frag);
				}
				try {
					HtmlDocument doc = WuluUtil.parseDocument(frag);
					doc.init(request, response, servletContext);
					doc.setRequest(request);
					doc.setResponse(response);
					doc.setServletContext(servletContext);
					doc.mergeFragments();
					doc.applyManipulatorsInMetaTag();
					doc.applyManipulatorTag();

					fragmentList.add(doc);
				} catch (JDOMException e) {
					logger.error(e, e);
				} catch (IOException e) {
					logger.error(e, e);
				}
			}

			List templateFragmentList = selectNodes("//html:*[@fragId]");
			for (int i = 0; i < templateFragmentList.size(); i++) {
				Element templateFrag = (Element) templateFragmentList.get(i);
				String fragId = templateFrag.getAttributeValue("fragId");

				for (HtmlDocument doc : fragmentList) {
					String xpath = "//html:*[@fragId='" + fragId + "']";
					Element frag = doc.selectNode(xpath);
					if (frag != null) {
						Element p = (Element) templateFrag.getParent();
						int j = p.indexOf(templateFrag);
						templateFrag.detach();
						frag.detach();
						p.addContent(j, frag);
						break;
					}
				}

			}
		}
	}

	public String toString() {
		return xmlPrettyPrinter.outputString(this);
	}
}
